﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sherwood.SignOn.Server.Models.Repositories;
using Sherwood.SignOn;
using Sherwood.Security;

namespace Sherwood.SignOn.Server.Models.Repositories.InMemory
{
    [Serializable]
    public class UserAccount : IUserAccount
    {
        /// <summary>
        /// Primary key for user account.
        /// </summary>
        public virtual Guid Id { get; set; }

        /// <summary>
        /// Account username. This value should be unique for each account and treated as non-case sensitive.
        /// </summary>
        public virtual string UserName { get; set; }

        /// <summary>
        /// Password hash represented in the following format:
        ///     algorithmName:hash
        ///     e.g. SHA256:[hash data]
        /// </summary>
        public virtual string PasswordHash { get; set; }

        /// <summary>
        /// Password hash for the password user wishes to have for their account represented in the following format:
        ///     algorithmName:hash
        ///     e.g. SHA256:[hash data]
        /// </summary>
        public virtual string ProposedPasswordHash { get; set; }

        /// <summary>
        /// User first name.
        /// </summary>
        public virtual string FirstName { get; set; }

        /// <summary>
        /// User last name.
        /// </summary>
        public virtual string LastName { get; set; }

        /// <summary>
        /// User gender.
        /// </summary>
        public virtual Gender Gender { get; set; }

        public virtual DateTime? DateOfBirth { get; set; }

        /// <summary>
        /// Account email address. Note that this is a unique field. 
        /// No two accounts should share the same email address.
        /// </summary>
        public virtual string Email { get; set; }

        /// <summary>
        /// The email address the user wishes to set their account to (before verfication)
        /// </summary>
        public virtual string ProposedEmail { get; set; }
        
        /// <summary>
        /// Account mobile number
        /// </summary>
        public virtual string Mobile { get; set; }

        /// <summary>
        /// .Net culture name for the user's preferred language.
        /// </summary>
        public virtual string CultureName1 { get; set; }

        /// <summary>
        /// .Net culture name for the user's preferred language.
        /// </summary>
        public virtual string CultureName2 { get; set; }


        /// <summary>
        /// .Net culture name for the user's preferred language.
        /// </summary>
        public virtual string CultureName3 { get; set; }

        /// <summary>
        /// ISO2 country code
        /// </summary>
        public virtual string Country { get; set; }

        /// <summary>
        /// Date account was created
        /// </summary>
        public virtual DateTime DateCreated { get; set; }

        /// <summary>
        /// Date account was last modified
        /// </summary>
        public virtual DateTime DateModified { get; set; }

        /// <summary>
        /// Indicates the verification status of the account.
        ///     Unverified: the account has not been verified
        ///     Verified: the account has been verified
        ///     ResetPassword: the account has been verified, but the user has requested a new verification code
        /// in order to reset the password.
        /// </summary>
        public virtual VerificationStatus VerificationStatus { get; set; }

        /// <summary>
        /// Url requested prior to creating a user account, or requesting a password reset. 
        /// This can be used to automatically redirect the user to their desired destination.
        /// </summary>
        public virtual string RequestedUrl { get; set; }

        public virtual string VerificationCode { get; set; }


        /// <summary>
        /// Specifies where the user account is enabled. Note that this is distinct from verification status.
        /// If an account is not enabled it usually means that the account has been disabled by as system
        /// administrator.
        /// </summary>
        public virtual bool IsEnabled { get; set; }

        public virtual IEnumerable<IUserSession> Sessions
        {
            get
            {
                return InMemoryRepository.UserSessions.All.Where(u => u.UserAccountId.Equals(Id));
            }
        }

        /// <summary>
        /// Generates a verification code (which an be used for verifying a user via an email link).
        /// The VerificationCode property is set, but this method does not change VerificationStatus.
        /// Usually, VerificationStatus should be changed at the same time.
        /// </summary>
        /// <returns></returns>
        public virtual string CreateVerificationCode()
        {
            VerificationCode = Guid.NewGuid().ToString();
            return VerificationCode;
        }

        /// <summary>
        /// Validates the verification code against the code stored in the VerificationCode property for the user.
        /// Verification status is not changed as part of this operation, but should usually be changed to "Verified"
        /// as part of a verification process.
        /// </summary>
        /// <param name="verificationCode"></param>
        /// <returns></returns>
        public virtual bool ValidateVerificationCode(string verificationCode)
        {
            return verificationCode != null && VerificationCode != null && VerificationCode.Equals(verificationCode.Trim());
        }

        /// <summary>
        /// Creates a SHA256 hash for the specified password and sets the PasswordHash property.
        /// </summary>
        /// <param name="password">Password to be hashed.</param>
        /// <returns>True if hash was sucessfully created, False if an error occured or password was invalid (e.g. null).</returns>
        public virtual bool SetPassword(string password)
        {
            try
            {
                string algorithm = "SHA256";
                string hash = Hash.ComputeHash(password, algorithm, null);
                PasswordHash = algorithm + ":" + hash;
                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Verifies the specified password against the PasswordHash property.
        /// </summary>
        /// <param name="password">Password to be validated</param>
        /// <returns>True if password corrosponds to the PasswordHash, False if password does not match or an error occured.</returns>
        public virtual bool VerifyPassword(string password)
        {
            if (!string.IsNullOrEmpty(PasswordHash))
            {
                string[] parts = PasswordHash.Split(':');
                string algorithm = parts[0];
                string hash = "";
                if (parts.Length > 0)
                {
                    for (int i = 1; i < parts.Length; i++)
                    {
                        hash = hash + ":" + parts[i];
                    }
                }
                hash = hash.TrimStart(':');
                if (hash.Length > 0)
                {
                    try
                    {
                        return Hash.VerifyHash(password, algorithm, hash);
                    }
                    catch
                    {
                        return false;
                    }
                }
            }
            return false;
        }


        /// <summary>
        /// User display name in the form: FirstName LastName
        /// </summary>
        public virtual string DisplayName
        {
            get
            {
                string firstName = FirstName != null ? FirstName : "";
                string lastName = LastName != null ? LastName : "";
                return (firstName + " " + lastName).Trim();
            }
        }


        public virtual void SetVerificationParameters(VerificationStatus verificationStatus, string requestedUrl, string verificationCode)
        {
            VerificationCode = verificationCode;
            VerificationStatus = verificationStatus;
            RequestedUrl = requestedUrl;
        }
        

        public virtual string SetProposedEmailAndPassword(string email, string password)
        {
            if (!string.IsNullOrEmpty(password))
            {
                try
                {
                    string algorithm = "SHA256";
                    string hash = Hash.ComputeHash(password, algorithm, null);
                    ProposedPasswordHash = algorithm + ":" + hash;
                }
                catch
                {
                    return null;
                }
            }
            ProposedEmail = email;
            string verificationCode = CreateVerificationCode();
            if (VerificationStatus == Sherwood.SignOn.VerificationStatus.Verified || VerificationStatus == Sherwood.SignOn.VerificationStatus.ResetPassword)
            {
                if (!Email.Equals(ProposedEmail) || !string.IsNullOrEmpty(password))
                {
                    VerificationStatus = Sherwood.SignOn.VerificationStatus.ResetPassword;
                }
                else
                {
                    VerificationStatus = Sherwood.SignOn.VerificationStatus.Verified;
                }
            }
            return verificationCode;
        }

        public virtual bool ValidateProposedEmailAndPasswordChange(string verificationCode)
        {
            if (ValidateVerificationCode(verificationCode))
            {
                if (!string.IsNullOrEmpty(ProposedEmail))
                {
                    string email = ProposedEmail.ToLower();
                    UserAccount existingUser = (from a in InMemoryRepository.UserAccounts.All where a.Email != null && a.Email.ToLower() == email select a).FirstOrDefault();
                    if (existingUser != null && !existingUser.Id.Equals(Id))
                    {
                        return false; //another user has the proposed email address! This could for example happen if the user decides to create a new account after trying to change an existing account.
                    }
                    Email = ProposedEmail;
                    ProposedEmail = "";
                }
                if (!string.IsNullOrEmpty(ProposedPasswordHash))
                {
                    PasswordHash = ProposedPasswordHash;
                    ProposedPasswordHash = "";
                }
                VerificationCode = "";
                return true;
            }
            return false;
        }


        public void SetEnabled(bool enabled)
        {
            IsEnabled = enabled;
        }
    }
}
